package com.yiban.demo.Global.AOP.Aspect;

import com.yiban.demo.Global.AOP.Annotation.LimitRequestDuration;
import com.yiban.demo.Global.Entity.ResponseEntity;
import com.yiban.demo.Util.JsonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;

@Aspect
@Component
public class LimitRequestDurationAspect {
    /**
     * redis key 为 LIMIT_PREFIX+访问ip+方法名构成
     * value为1（我还没想好用什么value）
     * 过期时间由 @LimitRequestDuration的 second值 决定
     */
    private final static String LIMIT_PREFIX = "limit";
    @Autowired
    private RedisTemplate redisTemplate;

    @Around("@annotation(com.yiban.demo.Global.AOP.Annotation.LimitRequestDuration)")
    public Object limit(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
       //获取间隔
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature())
                .getMethod();
        int seconds = method.getAnnotation(LimitRequestDuration.class)
                .seconds();
        /**
         * 从上下文获取当前请求,底层为ThreadLocal实现的
         * 从源码来看支持父子线程继承，但默认不开启，所以执行这个方法的线程必须和请求线程一致
         * 而且web多用线程池，基本各个线程没有”继承“关系
         * @see <a href="https://cloud.tencent.com/developer/article/1600755">Spring注入的成员属性HttpServletRequest是线程安全的吗</a>
         */

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        //获取真实ip，防止存在代理
        String ip;
        String forward = request.getHeader("x-forwarded-for");
        /**
         * request.getRemoteAddr()直接获取为上一个发送请求地址 若 A->B->service则获取的为B的地址
         * header的x-forwarded-for   若 A->B->service 则为x-forwarded-for:Aip,Bip，所以分割后获取用户ip地址
         * @see <a href ="https://www.jianshu.com/p/15f3498a7fad">HTTP 请求头中的 X-Forwarded-For</a>
         */
        ip = (forward == null ? request.getRemoteAddr() : forward.split(",")[0]);
        ValueOperations ops = redisTemplate.opsForValue();

        String keyName = LIMIT_PREFIX + ip + method.getName();
        //若存在就是拒绝访问（不执行这个Controller的方法）,上下文中获取Response对象返回错误信息
        if (ops.get(keyName) == null) {
            ops.set(keyName, 1, Duration.ofSeconds(seconds));
            return proceedingJoinPoint.proceed();
        }else {
            ResponseEntity.writeBack(requestAttributes.getResponse(), JsonUtil.defaultJsonify(ResponseEntity.Failure("操作的太快了")));
            return null;
        }
    }

}
